﻿using System;
using CoreEx.Common.Cache;
using CoreEx.Database.Metadata;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.IoC;

namespace CoreEx.Database.Mapping.Implementation
{
    /// <summary>
    /// A class that is capable of mapping a <see cref="Type"/> into a <see cref="TypeMappingInfo"/> instance.
    /// </summary>
    [Implements(typeof(ITypeInfoMapper))]
    public class TypeInfoMapper : ITypeInfoMapper, IInitialize
    {
        private ILazyCache<Type, TypeMappingInfo> _cache;

        /// <summary>
        /// Maps the <paramref name="type"/> to an <see cref="TypeMappingInfo"/> instance.
        /// </summary>
        /// <param name="type">The <see cref="Type"/> to map.</param>
        /// <returns><see cref="TypeMappingInfo"/></returns>
        public TypeMappingInfo MapFrom(Type type)
        {
            return _cache.GetValue(type, CreateTypeMapping);
        }

        private TypeMappingInfo CreateTypeMapping(Type type)
        {
            var tableInfo = TableMapper.MapFrom(type);
            ValidatePrimaryKey(type, tableInfo);
            var propertyMappingList = PropertyMapper.MapFrom(type, tableInfo);
            var typeMappingInfo = new TypeMappingInfo
            {
                ColumnMappings = propertyMappingList,
                TableInfo = tableInfo,
                Type = type
            };
            return typeMappingInfo;
        }

        private static void ValidatePrimaryKey(Type type, TableInfo tableInfo)
        {
            if (tableInfo.PrimaryKey == null)
                throw new MappingException(string.Format("Unable to map type '{0}'. The table '{1}' does not define a primary key", type.Name, tableInfo));
        }

        /// <summary>
        /// Gets or sets the <see cref="ITableInfoMapper"/> instance that is responsible 
        /// for mapping types to database tables.
        /// </summary>
        public ITableInfoMapper TableMapper { get; set; }
        
        /// <summary>
        /// Gets or sets the <see cref="IPropertyInfoMapper"/> instance that is responsible
        /// for mapping properties to database columns.
        /// </summary>
        public IPropertyInfoMapper PropertyMapper { get; set; }


        /// <summary>
        /// Initializes this <see cref="ITypeInfoMapper"/> instance.
        /// </summary>        
        public void Initialize(IServiceContainer source)
        {
            _cache = source.GetService<ILazyCache<Type, TypeMappingInfo>>();
            _cache.LifeSpan = TimeSpan.Zero;
            TableMapper = source.GetService<ITableInfoMapper>();
            PropertyMapper = source.GetService<IPropertyInfoMapper>();
        }
    }
}
